#ifndef _FNT_SLICE2CUBE_H_
#define _FNT_SLICE2CUBE_H_

#include <string>
#include <vector>
#include <deque>
#include <mutex>
#include <condition_variable>

#include <zlib.h>

#include "stream.h"

typedef struct tiff TIFF;

void createDirectory(const std::string& dir);
int getNumberOfCpus();
void checkImages(const std::vector<std::string>& inputfiles, uint64_t* pwidth, uint64_t* pheight, uint64_t* pdepth, int* pchannels, int* pbpc, std::vector<std::pair<const std::string*, int>>* inputpairs, bool skip_check, uint64_t dpeth_hint);

template <typename T>
class Slice2Cube {
	const std::vector<std::string>& inputfiles;
	const double xres, yres, zres;
	const uint64_t width, height, depth;
	const int channels;
	const int bps;
	uint64_t xsize, ysize, zsize;
	uint64_t dsw, dsh, dsd;
	uint32_t dsx, dsy, dsz;
	const std::string& outdir;
	const int64_t cachesize;
	const uint8_t*const xfunc;
	const int ncpu;
	const bool resume;
	bool ds_only;
	bool ds_max;

	struct Slices {
		std::vector<T*> bufs;
		uint64_t zidx;
		int scheduled;
		int unfinished;
		int total;
	};
	struct NrrdBlock {
		z_stream strm;
		char buf[BUFSIZ];
		std::string fn;
		NrrdBlock();
		~NrrdBlock();
	};

	bool all_checked;
	std::vector<std::pair<const std::string*, int>> inputpairs;
	uint64_t tiffidx;
	TIFF* tiffd;

	std::vector<std::vector<T>> imagebufs;
	std::vector<std::ofstream> tempfs;
	std::vector<OutputStream> nrrdfs;
	std::vector<std::string> tempfn;
	uint64_t start_idx_ds;
	std::vector<T> bufds;
	std::vector<uint64_t> bufdssum;
	std::vector<NrrdBlock> writers;

	bool error;
	bool finished;
	std::mutex mtx;
	std::condition_variable cv0, cv1;
	std::deque<Slices> readQue;
	std::deque<Slices> writeQue;
	std::deque<Slices> writeCubeQue;
	std::deque<Slices> writeDsQue;

	uint64_t readQueSize;
	uint64_t writeQueSize;
	uint64_t writeCubeQueSize;
	uint64_t writeDsQueSize;

	void startConvert();
	void startConvertToCubes();
	void startConvertWorker();
	void startConvertToCubesWorker();
	void getNextImage();
	void writeCatalogSimple();
	void writeCatalogCubes();

	void readInputFiles(Slices* slices, int jobi);
	void writeCubes(Slices* slices, int jobi);
	void deflateDownsample(Slices* slices, int jobi);
	void writeDownsample(Slices* slices, int jobi);

	public:
	void start() {
		startConvertToCubes();
	}
	Slice2Cube(const std::vector<std::string>& _inputfiles, std::vector<std::pair<const std::string*, int>>&& _inputpairs, double _xres, double _yres, double _zres, uint64_t _width, uint64_t _height, uint64_t _depth, int _channels, int _bps, uint64_t _xsize, uint64_t _ysize, uint64_t _zsize, uint32_t _dsx, uint32_t _dsy, uint32_t _dsz, const std::string& _outdir, int64_t _cachesize, const uint8_t* _xfunc, int _ncpu, bool _resume, bool _ds_only, bool _ds_max);
	~Slice2Cube() {
		closeTiffFiles();
	}

	void printConversionInfo();
	void getResumePosition(uint64_t* pstart_idx);
	void closeTiffFiles();
};

#endif
